﻿// UnicodeText.cpp: implementation of the CUnicodeText class.
//
// 유니코드 프로젝트를 기준으로 만들었음
// 즉, TCHAR == WCHAR임
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "UnicodeText.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CUnicodeText::CUnicodeText()
{
	lLen = 0;
	tcText = NULL;
}

CUnicodeText::~CUnicodeText()
{
	if (tcText) delete tcText;
}


bool CUnicodeText::bReadTextfile(LPCTSTR lpFilename, UINT uiCodePage)
{
	if (tcText) {
		delete tcText;
		tcText = NULL;
	}
	//유니코드 프로젝트를 기준으로 만들었음
	//즉, TCHAR == WCHAR임
	TCHAR *tUnicode;
	long lUnicodeLen;

	CFileStatus status;
	if (!CFile::GetStatus(lpFilename, status)) return false;

	size_t lSize = static_cast<size_t>(status.m_size);
	if (lSize<2) return false;

	BYTE *bBuf = new BYTE[lSize+2];
	if (!bBuf) return false;

	FILE *f;
	if (_tfopen_s(&f, lpFilename, _T("rb")) || !f) return false;

	fread(bBuf, sizeof(BYTE), lSize, f);
	fclose(f);

	bBuf[lSize] = 0;
	bBuf[lSize+1] = 0;
	int headersize;

	switch (iTextType(bBuf, lSize+1, headersize)) {
	case 0:
		// Unicode BOM

		//맨 앞의 0xfeff (0xff, 0xfe)를 뺀 길이
		lUnicodeLen = (lSize-headersize)/2;
		tUnicode = new TCHAR[lUnicodeLen+1];
		if (!tUnicode) {
			delete []bBuf;
			return false;
		}

		memcpy(tUnicode, bBuf+headersize, sizeof(TCHAR)*(lUnicodeLen+1));

		break;

	case 1:
		//1: ANSI
		tUnicode = new TCHAR[lSize+1];
		if (!tUnicode) {
			delete []bBuf;
			return false;
		}

		lUnicodeLen = MultiByteToWideChar(uiCodePage, 0, (const char *)bBuf, -1, tUnicode, lSize+1);
		// SRT의 코드페이지를 자동으로 인식하게 해보려 했는데, 사실상 불가능

		break;

	case 2:
		//2: UTF8
		tUnicode = tcUTF8toUnicode(bBuf, lSize, lUnicodeLen);

		break;

	//case 3:
	default:	// 정적 테스트 결과에서 초기화되지 않았다는 경고를 피하기 위함
		//3: Signed UTF8
		tUnicode = tcUTF8toUnicode(bBuf+headersize, lSize-headersize, lUnicodeLen);

		break;
	}

	delete []bBuf;

	// 이 시점에서 tUnicode[]에는 lUnicodeLen 개의 유니코드 데이터가 저장되어 있음

	// \r, \n 정리
	// 0x0d, 0x0a -> 0x0d로
	// 0x0a 단독 -> 0x0d로
	// 즉, 0x0a는 완전 제거

	TCHAR *tEnterRemoved = new TCHAR[lUnicodeLen+1];
	TCHAR *t1 = tEnterRemoved;
	TCHAR *t2 = tUnicode;
	long lEnterRemovedLen = 0;

	while (*t2) {
		if (
		    (*t2 == _TCHAR('\r')) &&
		    (t2[1] == _TCHAR('\n'))
		) {
			*t1 = _TCHAR('\r');

			t2+=2;
			t1++;
			lEnterRemovedLen++;
		} else if (
		    (*t2 == _TCHAR('\r')) ||
		    (*t2 == _TCHAR('\n'))
		) {
			*t1 = _TCHAR('\r');

			t1++;
			t2++;
			lEnterRemovedLen++;
		} else {
			*t1 = *t2;

			t1++;
			t2++;
			lEnterRemovedLen++;
		}
	}
	*t1 = _TCHAR(0);

	delete []tUnicode;

	tcText = tEnterRemoved;
	lLen = lEnterRemovedLen;

	return true;
}

int CUnicodeText::iTextType(BYTE *b, long lSize, int &headersize)
{
	headersize = 0;

	//0: Unicode BOM
	//1: ANSI
	//2: UTF8
	//3: Signed UTF8

	// 0이나 3에서 멍청하게 헤더가 여러번 등장하는 경우도 대비
	// 뭐 이렇게까지 해줄 필요가 있나 모르겠지만.

	if (lSize>=2 && b[0] == 0xff && b[1] == 0xfe) {
		int pos=0;
		while (lSize>=(pos+2) && b[pos] == 0xff && b[pos+1] == 0xfe) {
			pos+=2;
		}
		headersize = pos;
		return 0;
	}
	if (lSize>=3 && b[0] == 0xef && b[1] == 0xbb && b[2] == 0xbf) {
		int pos=0;
		while (lSize>=(pos+3) && b[pos] == 0xef && b[pos+1] == 0xbb && b[pos+2] == 0xbf) {
			pos+=3;
		}
		headersize = pos;
		return 3;
	}

	long l;
	bool bFound = false;
	for (l=0; l<lSize; l++) {
		if (b[l] & 0x80) {
			bFound = true;
			break;
		}
	}

	// 전체가 7비트로 되어있으면 닥치고 ANSI
	if (!bFound) return 1;

	l=0;

	//실제 동작하지 않은 라인
	//bFound = false;	// UTF8로 인코딩되지 않은 것을 찾았는가?
	while (l<lSize) {
		if ((b[l] & 0x80) == 0) {
			l++;
			continue;
		}

		int i10count;
		if ((b[l] & 0xe0) == 0xc0) i10count = 1;
		else if ((b[l] & 0xf0) == 0xe0) i10count = 2;
		else if ((b[l] & 0xf8) == 0xf0) i10count = 3;
		//16비트 유니코드만 수용함
		else {
			// UTF8 첫 바이트가 아닌 놈이 발견되면 ANSI
			return 1;
		}

		for (long ll = 0; ll<i10count; ll++) {
			// UTF8 두번째 이후 바이트가 아닌 놈이 발견되면 ANSI
			if ((b[l+ll+1] & 0xc0) != 0x80) return 1;
		}

		l+=(i10count+1);
	}

	// 모든 테스트를 패스했으면 UTF8
	return 2;
}

TCHAR * CUnicodeText::tcUTF8toUnicode(BYTE *bUTF8, long lUTF8, long &lUnicode)
{
	TCHAR *tcRet = new TCHAR[lUTF8+1];
	long l1=0, l2=0;
	while (bUTF8[l1]) {
		if (!(bUTF8[l1] & 0x80)) {
			tcRet[l2] = (TCHAR)bUTF8[l1];
			l1++;
		} else if ((bUTF8[l1] & 0xe0) == 0xc0) {
			tcRet[l2] = (((TCHAR)(bUTF8[l1] & 0x1f)) << 6) | ((TCHAR)(bUTF8[l1+1] & 0x3f));
			l1+=2;
		} else if ((bUTF8[l1] & 0xf0) == 0xe0) {
			tcRet[l2] = (((TCHAR)(bUTF8[l1] & 0x0f)) << 12) | (((TCHAR)(bUTF8[l1+1] & 0x3f)) << 6) | ((TCHAR)(bUTF8[l1+2] & 0x3f));
			l1+=3;
		} else {
			l1++;
		}
		l2++;
	}
	tcRet[l2] = _TCHAR(0);
	lUnicode = l2;

	return tcRet;
}

void CUnicodeText::WriteTextfile(LPCTSTR lpFilename)
{
	FILE *f;
	if (_tfopen_s(&f, lpFilename, _T("wb")) || !f) return;

	// UTF8로 변환 후 출력
	long lB;
	BYTE *b = bUnicodetoUTF8(tcText, lLen, lB);

	// 인코딩은 무조건 Signed-UTF8
	static BYTE bUTF8SIGN[] = {0xef, 0xbb, 0xbf};
	fwrite(bUTF8SIGN, sizeof(BYTE), 3, f);

	fwrite(b, sizeof(BYTE), lB, f);
	delete b;

	fclose(f);
}


BYTE * CUnicodeText::bUnicodetoUTF8(TCHAR *tcUnicode, long lUnicode, long &lUTF8)
{
	BYTE *b = new BYTE[lUnicode*3+1];
	if (!b) return NULL;

	long lB=0;

	for (long l=0; l<lUnicode; l++) {
		if (tcUnicode[l] == _TCHAR('\r')) {
			b[lB++] = _TCHAR('\r');
			b[lB++] = _TCHAR('\n');
		} else if ((tcUnicode[l] & 0xff80) == 0) b[lB++] = BYTE(tcUnicode[l]);
		else if ((tcUnicode[l] & 0xf800) == 0) {
			b[lB++] = BYTE((tcUnicode[l] >> 6) & 0x1f | 0xc0);
			b[lB++] = BYTE((tcUnicode[l]) & 0x3f | 0x80);
		} else {
			b[lB++] = BYTE((tcUnicode[l] >> 12) & 0x0f | 0xe0);
			b[lB++] = BYTE((tcUnicode[l] >> 6) & 0x3f | 0x80);
			b[lB++] = BYTE((tcUnicode[l]) & 0x3f | 0x80);
		}

		if (!(tcUnicode[l])) break;
	}

	lUTF8 = lB;

	return b;
}

//이건 사실상 쓸 수가 없음

UINT CUnicodeText::uiGetCodePage(BYTE *b, long lSize)
{
	// 한글(949), Simplified Chinese(936)만 확인 가능
	// 그 외엔 CP_ACP
	// 다른 언어는 나중에 필요하면…

	BYTE *bTemp = new BYTE[lSize+4];
	ZeroMemory(bTemp, lSize+4);
	memcpy(bTemp, b, lSize);

	// 일단 한글인지 확인
	// http://www.w3c.or.kr/i18n/hangul-i18n/ko-code.html
	// 1. A1~FE / A1~FE
	// 2. 81~C6 / 41~FE

	bool bIsKorean = true;
	for (int i=0; i<lSize; i++) {
		// 영어는 일단 패스
		if (bTemp[i] & 0x80) {
			if (
				((bTemp[i]>=0xa1) && (bTemp[i]<=0xfe) && (bTemp[i+1]>=0xa1) && (bTemp[i+1]<=0xfe)) ||	//완성형
				((bTemp[i]>=0x81) && (bTemp[i]<=0xc6) && (bTemp[i+1]>=0x41) && (bTemp[i+1]<=0xfe))		//확장완성형
				) {
				i++;
			} else {
				bIsKorean = false;
				break;
			}
		}
	}

	if (bIsKorean) {
		delete []bTemp;
		return 949;
	}

	// Simplified Chinese인지 확인
	// 1. 81~FE / 40~FE
	// 2. 81~FE / 30~39 / 81~FE / 30~39

	bool bIsSimplifiedChinese = true;
	for (int i=0; i<lSize; i++) {
		// 영어는 일단 패스
		if (bTemp[i] & 0x80) {
			if ((bTemp[i]>=0x81) && (bTemp[i]<=0xfe) && (bTemp[i+1]>=0x40) && (bTemp[i+1]<=0xfe)) {
				i++;
			} else if ((bTemp[i]>=0x81) && (bTemp[i]<=0xfe) && (bTemp[i+1]>=0x30) && (bTemp[i+1]<=0x39) && (bTemp[i+2]>=0x81) && (bTemp[i+2]<=0xfe) && (bTemp[i+3]>=0x30) && (bTemp[i+3]<=0x39)) {
				i+=3;
			} else {
				bIsSimplifiedChinese = false;
				break;
			}
		}
	}

	delete []bTemp;

	return (bIsSimplifiedChinese)?936:CP_ACP;
}

int CUnicodeText::iRead1Line(TCHAR *tcUnicode, int iLen, int iFrom, int &iNext, CString &csLine)
{
	//리턴값은 끝내기 엔터(0x0d)를 제외한 길이
	if (iFrom>=iLen) return -1;
	
	csLine = _T("");
	int iReadLen=0;
	
	for (int i=iFrom; i<iLen; i++) {
		if (tcUnicode[i] == _TCHAR(0x0d)) {
			iNext = i+1;
			return iReadLen;
		}
		
		csLine += tcUnicode[i];
		iReadLen++;
	}
	
	//엔터가 없는 파일의 끝
	
	iNext = iLen;
	return iReadLen;
}
